package arithmetic.leetCode;

import java.util.HashMap;
import java.util.Objects;

/**
 * @see https://www.jianshu.com/p/86f2d8911470
 *
 * 运用你所掌握的数据结构，设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作： 获取数据 get 和 写入数据 put 。
 *
 * 获取数据 get(key) - 如果密钥 (key) 存在于缓存中，则获取密钥的值（总是正数），否则返回 -1。
 * 写入数据 put(key, value) - 如果密钥不存在，则写入其数据值。当缓存容量达到上限时，它应该在写入新数据之前删除最近最少使用的数据值，从而为新的数据值留出空间。
 *
 * 进阶:
 *
 * 你是否可以在 O(1) 时间复杂度内完成这两种操作？
 *
 *
 * 这道题在今日头条、快手或者硅谷的公司中是比较常见的，代码要写的还蛮多的，难度也是hard级别。
 *
 * 最重要的是LRU 这个策略怎么去实现,
 * 很容易想到用一个链表去实现最近使用的放在链表的最前面。
 * 比如get一个元素,相当于被使用过了，这个时候它需要放到最前面，再返回值,
 * set同理。
 * 那如何把一个链表的中间元素，快速的放到链表的开头呢？
 * 很自然的我们想到了双端链表。
 *
 * @author dycong
 * @date 2019/10/11 8:57
 */
public class LruCache {

    private int count;

    private int capacity;

    DLinkedNode head, tail;

    /**
     * todo 整体的设计思路是，可以使用 HashMap 存储 key，这样可以做到 save 和 get key的时间都是 O(1)，而 HashMap 的 Value 指向双向链表实现的 LRU 的 Node 节点
     */
    private HashMap<String, DLinkedNode> cache = new HashMap<>();


    public LruCache(int capacity) {
        this.capacity = capacity;

        head = new DLinkedNode();
        head.pre = null;

        tail = new DLinkedNode();
        tail.post = null;

        head.post = tail;
        tail.pre = head;
    }

    /**
     * 添加头节点
     *
     * @param node
     */
    private void addHead(DLinkedNode node) {
        node.pre = head;
        node.post = head.post;

        head.post.pre = node;
        head.post = node;
    }

    /**
     * 移除指定节点
     *
     * @param node
     */
    private void removeNode(DLinkedNode node) {
        if (node == head || node == tail) {
            return;
        }
        DLinkedNode pre = node.pre;
        DLinkedNode post = node.post;

        pre.post = post;
        post.pre = pre;
    }

    /**
     * 摘除一个节点,并且将它移动到开头
     *
     * @param node
     */
    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addHead(node);
    }

    /**
     * 移除一个尾节点
     */
    private DLinkedNode removeTail() {
        DLinkedNode oldLastNode = tail.pre;
        if (oldLastNode == head) return null;
        removeNode(tail.pre);
        return oldLastNode;
    }


    public int get(String key) {
        DLinkedNode node = cache.get(key);
        if (Objects.isNull(node)) {
            return -1;
        }
        moveToHead(node);
        return node.value;
    }

    public int put(String key, int value) {
        DLinkedNode node = cache.get(key);
        if (Objects.isNull(node)) {
            //不存在
            DLinkedNode newNode = new DLinkedNode(key, value);
            cache.put(key, newNode);
            addHead(newNode);
            ++count;

            // TODO: 2019/10/11 遗忘点
            if (count > capacity) {
                DLinkedNode oldLastNode = removeTail();
                if (Objects.nonNull(oldLastNode)) {
                    cache.remove(oldLastNode.key);
                }
            }

            return -1;
        } else {
            //存在
            int oldValue = node.value;
            // TODO: 2019/10/11 简单替换即可
            node.value = value;
            moveToHead(node);
            return oldValue;
        }
    }

    @Override
    public String toString() {
        StringBuilder lru = new StringBuilder();
        DLinkedNode node = head.post;
        while (Objects.nonNull(node) && node != tail) {
            lru.append(node.key).append(" : ").append(node.value).append(",");
            node = node.post;
        }
        return "LruCache{" +
                "count=" + count +
                ", capacity=" + capacity +
                ", lru=" + lru.toString() +
                '}';
    }

    public static void main(String[] args) {
        LruCache lruCache = new LruCache(3);

        for (int i = 0; i < 10; i++) {
            lruCache.put("" + i, i);
            System.out.println("i:" + i + "   cache:" + lruCache.toString());
        }
        System.out.print(lruCache.get("7"));
        System.out.println("  cache:" + lruCache.toString());
    }
}
